let obj1 = {
    name: '张三',
    age: 25,
    hobby: {
        music: 100,
        jump: 80
    }
}
obj1.AAA = obj1

let obj2 = {
    name: '李四',
    age: 22,
    sex: 0,
    hobby: {
        read: 100,
        music: 90
    }
}
obj2.AAA = obj2

let obj3 = {
    name: '王五',
    age: 20,
    height: '158cm',
    score: {
        math: 100,
        chinese: 90
    }
}

/* 
// Object.assign：处理的浅合并「只合并第一级，或者不论属性值是啥类型，直接用obj2中的值，替换obj1中的值」
let obj = Object.assign({}, obj1, obj2)
console.log(obj) 
*/

/* 
 merge：实现数组和对象的合并
   @params
     deep：布尔 false浅合并 true深合并
     target：要被合并/替换的对象
     objs：替换target的“对象集合”
   @return 
     target 被替换后的对象
 */
const isArrayOrObject = obj => {
    return _.isArray(obj) || _.isPlainObject(obj)
}
const merge = function merge(exist, deep, target, ...objs) {
    if (!isArrayOrObject(target)) throw new TypeError(`被替换的目标必须是数组或者纯粹对象`)
    if (objs.length === 0) return target //如果不具备替换的对象，则直接返回目标对象
    // 迭代 objs 对象集合中的每一项
    objs.forEach(obj => {
        // obj:每一个替换的对象，我们需要拿它去替换目标对象target「但要保证它也得是数组/对象」
        if (!isArrayOrObject(obj)) return
        // 防止套娃
        if (exist.indexOf(obj) >= 0) return
        exist.push(obj)
        // 依次迭代这个对象中的每个成员，去替换target目标对象中，同名的这个成员
        _.each(obj, (value, key) => {
            let targetValue = target[key]
            if (deep && isArrayOrObject(targetValue) && isArrayOrObject(value)) {
                // 把这两个对象，基于递归的方式，再次合并一下
                target[key] = merge(exist, deep, targetValue, value)
                return
            }
            // 浅合并「或者直接让 替换对象值 替换 目标对象值」
            target[key] = value
        })
    })
    return target
}

// 供外界调用的深浅合并方法
const def = function def(obj, key, value) {
    Object.defineProperty(obj, key, {
        configurable: true,
        writable: true,
        enumerable: false,
        value
    })
}
def(Object, 'assign', (...params) => merge([], false, ...params))
def(Object, 'assignDeep', (...params) => merge([], true, ...params))

// 测试
console.log(Object.assign({}, obj1, obj2, obj3))  //->merge(false, {}, obj1, obj2, obj3)
console.log(Object.assignDeep({}, obj1, obj2, obj3)) //->merge(true, {}, obj1, obj2, obj3)